
package {{package}};

import io.helidon.common.configurable.ThreadPoolSupplier;
import io.helidon.common.http.DataChunk;
import io.helidon.common.http.Http;
import io.helidon.common.http.MediaType;
import io.helidon.common.reactive.IoMulti;
import io.helidon.media.multipart.ContentDisposition;
import io.helidon.media.multipart.ReadableBodyPart;
import io.helidon.webserver.ResponseHeaders;
import io.helidon.webserver.Routing;
import io.helidon.webserver.ServerRequest;
import io.helidon.webserver.ServerResponse;
import io.helidon.webserver.Service;
import jakarta.json.Json;
import jakarta.json.JsonArrayBuilder;
import jakarta.json.JsonBuilderFactory;

import java.nio.file.Path;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.ExecutorService;

/**
 * File service.
 */
public final class FileService implements Service {

    private static final JsonBuilderFactory JSON_FACTORY = Json.createBuilderFactory(Map.of());
    private final FileStorage storage;
    private final ExecutorService executor = ThreadPoolSupplier.create("multipart-thread-pool").get();


    /**
     * Create a new file upload service instance.
     */
    FileService() {
        storage = new FileStorage();
    }

    @Override
    public void update(Routing.Rules rules) {
        rules.get("/", this::list)
                .get("/{fname}", this::download)
                .post("/", this::upload);
    }

    private void list(ServerRequest req, ServerResponse res) {
        JsonArrayBuilder arrayBuilder = JSON_FACTORY.createArrayBuilder();
        storage.listFiles().forEach(arrayBuilder::add);
        res.send(JSON_FACTORY.createObjectBuilder().add("files", arrayBuilder).build());
    }

    private void download(ServerRequest req, ServerResponse res) {
        Path filePath = storage.lookup(req.path().param("fname"));
        ResponseHeaders headers = res.headers();
        headers.contentType(MediaType.APPLICATION_OCTET_STREAM);
        headers.put(Http.Header.CONTENT_DISPOSITION, ContentDisposition.builder()
                .filename(filePath.getFileName().toString())
                .build()
                .toString());
        res.send(filePath);
    }

    private void upload(ServerRequest req, ServerResponse res) {
        req.content().asStream(ReadableBodyPart.class)
           .forEach(part -> {
               if ("file[]".equals(part.name())) {
                   part.content().map(DataChunk::data)
                       .flatMapIterable(Arrays::asList)
                       .to(IoMulti.writeToFile(storage.create(part.filename()))
                                  .executor(executor)
                                  .build());
               } else {
                   // when streaming unconsumed parts needs to be drained
                   part.drain();
               }
           })
           .onError(res::send)
           .onComplete(() -> {
               res.status(Http.Status.OK_200);
               res.send();
           }).ignoreElement();
    }
}
